// src/utils/crypto.ts
const CRYPTO_ALGORITHM = "AES-GCM";
const PBKDF2_ITERATIONS = 100000;
const SALT_LENGTH = 16;
const IV_LENGTH = 12;

export class SessionCrypto {
	private static async deriveKey(password: string, salt: Uint8Array): Promise<CryptoKey> {
		const keyMaterial = await crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]);

		return crypto.subtle.deriveKey(
			{
				name: "PBKDF2",
				salt,
				iterations: PBKDF2_ITERATIONS,
				hash: "SHA-256",
			},
			keyMaterial,
			{ name: CRYPTO_ALGORITHM, length: 256 },
			false,
			["encrypt", "decrypt"]
		);
	}

	static async encrypt(data: string): Promise<string> {
		const password = import.meta.env.VITE_CRYPTO_SECRET;
		if (!password) throw new Error("Missing encryption password");

		const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
		const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
		const key = await this.deriveKey(password, salt);

		const encrypted = await crypto.subtle.encrypt({ name: CRYPTO_ALGORITHM, iv }, key, new TextEncoder().encode(data));

		// 数据结构：salt(16) + iv(12) + cipher
		const combined = new Uint8Array(SALT_LENGTH + IV_LENGTH + encrypted.byteLength);
		combined.set(salt, 0);
		combined.set(iv, SALT_LENGTH);
		combined.set(new Uint8Array(encrypted), SALT_LENGTH + IV_LENGTH);

		return btoa(String.fromCharCode(...combined));
	}

	static async decrypt(encrypted: string): Promise<string> {
		const password = import.meta.env.VITE_CRYPTO_SECRET;
		if (!password) throw new Error("Missing decryption password");

		const buffer = Uint8Array.from(atob(encrypted), c => c.charCodeAt(0));

		const salt = buffer.slice(0, SALT_LENGTH);
		const iv = buffer.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
		const cipher = buffer.slice(SALT_LENGTH + IV_LENGTH);

		const key = await this.deriveKey(password, salt);

		const decrypted = await crypto.subtle.decrypt({ name: CRYPTO_ALGORITHM, iv }, key, cipher);

		return new TextDecoder().decode(decrypted);
	}
}
